//------------------------------------------------------------------------------
//SFWK Framework
//Copyright (C) 2011 SQLI

//This program is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program.  If not, see <http://www.gnu.org/licenses/>.
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Web;
using System.Threading;
using System.Reflection;
using System.Data.Objects;
using System.Data.Linq;
using SFWK.Core;

namespace SFWK.DAO
{
    /// <summary>
    /// Factory of LINK data context
    /// Data Context factory, depending on processing environment,
    /// Stores data context in HttpContext or in processing Thread
    /// 
    /// This class provides several static methods for loading DataContext objects 
    /// in a variety of ways. You can load the data context as normal one new instance
    /// at a time, or you can choose to use one of the scoped factory methods that
    /// can scope the DataContext to a WebRequest or a Thread context (in a WinForm app
    /// for example).
    /// 
    /// Using scoped variants can be more efficient in some scenarios and allows passing
    /// a DataContext across multiple otherwise unrelated components so that the change
    /// context can be shared. 
    /// </summary>
    public class DataContextFactory
    {
        #region Accessors
        /// <summary>
        /// Key to thread context
        /// </summary>
        private static string _threadContextKey(Type type)
        {
            return RegisterKey(String.Format("{0}_{1}_{2}", "__WRSCDC_", type.Name, Thread.CurrentContext.ContextID.ToString()));
        }

        private static HashSet<string> _recordedKeys = new HashSet<string>();
        private static string RegisterKey(string key)
        {
            lock (typeof(DataContextFactory))
            {
                if (_recordedKeys.Contains(key)) return key;
                _recordedKeys.Add(key);
                return key;
            }
        }


        /// <summary>
        /// Key to http context
        /// </summary>
        private static string _httpContextKey(Type type)
        {
            return RegisterKey("__WRSCDC_" + type.Name + HttpContext.Current.GetHashCode().ToString("x") + Thread.CurrentContext.ContextID.ToString());
        }
        #endregion

        #region Get Context
        /// <summary>
        /// Returns either Web Request scoped DataContext or a Thread scoped
        /// request object if not running a Web request (ie. HttpContext.Current)
        /// is not available.
        /// </summary>
        /// <typeparam name="TDataContext"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public static TDataContext GetScopedDataContext<TDataContext>()
        where TDataContext : IDisposable
        {

            if (HttpContext.Current != null)
                return (TDataContext)GetWebRequestScopedDataContextInternal(typeof(TDataContext), null);

            return (TDataContext)GetThreadScopedDataContextInternal<TDataContext>();
        }

        /// <summary>
        /// Internal method that handles creating a context that is scoped to the HttpContext Items collection
        /// by creating and holding the DataContext there.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="key"></param>
        /// <param name="connectionString"></param>
        /// <returns></returns>
        static object GetWebRequestScopedDataContextInternal(Type type, string connectionString)
        {
            object context;

            if (HttpContext.Current == null)
            {
                if (connectionString == null)
                    context = Activator.CreateInstance(type);
                else
                    context = Activator.CreateInstance(type, connectionString);

                return context;
            }

            context = HttpContext.Current.Items[_httpContextKey(type)];
            if (context == null)
            {
                context = _createInstance(type, connectionString);

                if (context != null)
                {
                    HttpContext.Current.Items[_httpContextKey(type)] = context;

                }
            }

            return context;
        }

        /// <summary>
        /// Creates a Thread Scoped DataContext object that can be reused.
        /// The DataContext is stored in Thread local storage.
        /// </summary>
        /// <typeparam name="TDataContext"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        static T GetThreadScopedDataContextInternal<T>()
        where T : IDisposable
        {
            LocalDataStoreSlot dataStoreSlot = Thread.GetNamedDataSlot(_threadContextKey(typeof(T)));
            if (dataStoreSlot == null)
                dataStoreSlot = Thread.AllocateNamedDataSlot(_threadContextKey(typeof(T)));
            object threadData = Thread.GetData(dataStoreSlot);

            if (threadData != null)
            {
                //LogHelper.Log(threadData, typeof(T).Name, "Returning Scoped DataContext", ELogSeverity.Information);
                return (T)threadData;
            }
            else
            {
                //LogHelper.Log(threadData, typeof(T).Name, "Creating Scoped DataContext", ELogSeverity.Information);
                T obj = Activator.CreateInstance<T>();
                Thread.SetData(dataStoreSlot, obj);
                return obj;
            }
        }
        #endregion

        #region private methods
        /// <summary>
        /// Create an instance of an object of the desired type
        /// </summary>
        /// <param name="type"></param>
        /// <param name="connectionString"></param>
        /// <returns></returns>
        private static object _createInstance(Type type, string connectionString)
        {
            object context = null;
            if (connectionString == null)
                context = Activator.CreateInstance(type/*, HttpContext.Current.User*/);
            else
                context = Activator.CreateInstance(type, connectionString/*, HttpContext.Current.User*/);
            return context;
        }

        /// <summary>
        /// Checks if a thread exists
        /// </summary>
        /// <returns></returns>
        static internal bool ExistThreadData(Type type)
        {
            return (Thread.GetNamedDataSlot(_threadContextKey(type)) != null);
        }
        #endregion

        #region Dispose Multiple Data Context
        /// <summary>
        /// Dispose Multiple data Context
        /// </summary>
        /// <param name="dataContextAssemblyName"></param>
        public static void DisposeScopedDataContext()
        {
            if (HttpContext.Current != null)
            {
                DisposeWebRequestScopedDataContextInternal();
            }
            else
                DisposeThreadScopedDataContextInternal();
        }


        /// <summary>
        /// Dispose Http mode DataContext
        /// </summary>
        private static void DisposeWebRequestScopedDataContextInternal()
        {

            IDisposable context;

            if (HttpContext.Current != null)
            {
                foreach (string key in _recordedKeys)
                {
                    object value = HttpContext.Current.Items[key];
                    if (value != null)
                    {
                        ///Search for stored objects of type DataContext for LINQ2SQL or EF4
                        if ((value.GetType().BaseType == typeof(ObjectContext)) || (value.GetType().BaseType == typeof(DataContext)))
                        {
                            try
                            {
                                context = (IDisposable)value;
                                if (context != null)
                                {
                                    context.Dispose();
                                    context = null;
                                    HttpContext.Current.Items[key] = null;
                                    HttpContext.Current.Items.Remove(key);
                                }
                            }
                            catch (Exception ex)
                            {
                                string s = ex.Message;
                            }
                        }
                    }
                }

            }
        }

        /// <summary>
        /// Dispose Thread mode DataContext
        /// </summary>
        private static void DisposeThreadScopedDataContextInternal()
        {

            foreach (string regkey in _recordedKeys)
            {
                LocalDataStoreSlot threadData = Thread.GetNamedDataSlot(regkey);
                if (threadData != null)
                {
                    IDisposable context = (IDisposable)Thread.GetData(threadData);
                    if (context != null)
                    {
                        //LogHelper.Log(threadData, regkey, "DISPOSING Scoped DataContext", ELogSeverity.Information);

                        context.Dispose();
                        context = null;
                    }
                    Thread.FreeNamedDataSlot(regkey);
                }
            }

        }





        #endregion Dispose Multiple Data Context

    }
}
